// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

// SampleSpellingClient.cpp : Implementation of a sample spell checking client
#include "pch.h"
#include "onspellcheckerchanged.h"
#include "util.h"
#include "SpellingClient.h"
#include "spellprint.h"
#include "commands.h"

#include <spellcheck.h>
#include <stdio.h>
#include <objidl.h>
#include <fcntl.h>
#include <io.h>
#include <strsafe.h>

std::vector<PCWSTR> gVectorErrors;


HRESULT RunCommandLoop(_In_ ISpellChecker* spellChecker)
{
    wprintf(L"Commands:\n");
    wprintf(L"quit - Quit\n");
    wprintf(L"add <word> - Add word\n");
    wprintf(L"ac <word> <word> - Add autocorrect pair\n");
    wprintf(L"ign <word> - Ignore word\n");
    wprintf(L"chkb <text> - Check text (batch - pasted text or file open)\n");
    wprintf(L"chk <text> - Check text (as you type)\n");

    HRESULT hr = S_OK;
    while (SUCCEEDED(hr))
    {
        wprintf(L"> ");
        wchar_t line[MAX_PATH];
        hr = StringCchGets(line, ARRAYSIZE(line));
        
        if (SUCCEEDED(hr))
        {
            wchar_t command[MAX_PATH];
            int count = swscanf_s(line, L"%s", command, static_cast<unsigned int>(ARRAYSIZE(command)));
            hr = (count == 1) ? hr : E_FAIL;
            if (SUCCEEDED(hr))
            {
                _Analysis_assume_nullterminated_(command);
                const size_t lineSize = wcslen(line);
                const size_t cmdSize = wcslen(command);
                if (cmdSize == lineSize)
                {
                    if (wcscmp(L"quit", command) == 0)
                    {
                        break;
                    }
                    else
                    {
                        wprintf(L"Invalid command\n");
                    }
                }
                else
                {
                    PCWSTR buffer = line + wcslen(command);

                    if (wcscmp(L"add", command) == 0)
                    {
                        hr = AddCommand(spellChecker, buffer);
                    }
                    else if (wcscmp(L"ac", command) == 0)
                    {
                        hr = AutoCorrectCommand(spellChecker, buffer);
                    }
                    else if (wcscmp(L"ign", command) == 0)
                    {
                        hr = IgnoreCommand(spellChecker, buffer);
                    }
                    else if (wcscmp(L"chkb", command) == 0)
                    {
                        hr = CheckCommand(spellChecker, buffer);
                    }
                    else if (wcscmp(L"chk", command) == 0)
                    {
                        hr = CheckAsYouTypeCommand(spellChecker, buffer);
                    }
                    else
                    {
                        wprintf(L"Invalid command\n");
                    }
                }
            }
        }
    }

    PrintErrorIfFailed(L"RunCommandLoop", hr);
    return hr;
}

HRESULT RunSpellCheckingLoop(_In_ ISpellChecker* spellChecker)
{
    OnSpellCheckerChanged* eventListener = nullptr;
    HRESULT hr = PrintInfoAndOptions(spellChecker);
    if (SUCCEEDED(hr))
    {
        hr = OnSpellCheckerChanged::StartListeningToChangeEvents(spellChecker, &eventListener);
    }

    if (SUCCEEDED(hr))
    {
        hr = RunCommandLoop(spellChecker);
    }

    if (nullptr != eventListener)
    {
        OnSpellCheckerChanged::StopListeningToChangeEvents(spellChecker, eventListener);
    }

    PrintErrorIfFailed(L"RunSpellCheckingLoop", hr);
    return hr;
}

HRESULT StartSpellCheckingSession(_In_ ISpellCheckerFactory* spellCheckerFactory, _In_ PCWSTR languageTag)
{
    BOOL isSupported = FALSE;
    HRESULT hr = spellCheckerFactory->IsSupported(languageTag, &isSupported);
    if (SUCCEEDED(hr))
    {
        if (FALSE == isSupported)
        {
            wprintf(L"Language tag %s is not supported.\n", languageTag);
        }
        else
        {
            ISpellChecker* spellChecker = nullptr;
            hr = spellCheckerFactory->CreateSpellChecker(languageTag, &spellChecker);

            if (SUCCEEDED(hr))
            {
                hr = RunSpellCheckingLoop(spellChecker);
            }

            if (nullptr != spellChecker)
            {
                spellChecker->Release();
            }
        }
    }

    PrintErrorIfFailed(L"StartSpellCheckingSession", hr);
    return hr;
}

inline HRESULT getEnumString(_In_ IEnumString* enumString, _In_opt_ PCWSTR prefixText, _Inout_ std::vector<PCWSTR>& vectorStrings)
{
    HRESULT hr = S_OK;
    while (S_OK == hr)
    {
        LPOLESTR string = nullptr;
        hr = enumString->Next(1, &string, nullptr);

        if (S_OK == hr)
        {
			vectorStrings.push_back(string);
            /*if (nullptr == prefixText)
            {
                vectorStrings.push_back(string);
            }
            else
            {
				wchar_t str[MAX_PATH];
				swprintf(str, wcslen(prefixText) + wcslen(string), L"%s%s", prefixText, string);
				vectorStrings.push_back(str);
            }*/
            //CoTaskMemFree(string);
        }
    }

	if (FAILED(hr))
		gVectorErrors.push_back(L"getEnumString failed.");
    return (SUCCEEDED(hr) ? S_OK : hr);
}

inline HRESULT getSpellingError(_In_ ISpellChecker* spellChecker, _In_ PCWSTR text, _In_ ISpellingError* spellingError, 
								std::vector<PCWSTR>& vectorSuggestions, PWSTR& replacement)
{
    ULONG startIndex = 0;
    ULONG errorLength = 0;
    CORRECTIVE_ACTION correctiveAction = CORRECTIVE_ACTION_NONE;

    HRESULT hr = spellingError->get_StartIndex(&startIndex);
    if (SUCCEEDED(hr))
    {
        hr = spellingError->get_Length(&errorLength);
    }

    if(SUCCEEDED(hr))
    {
        hr = spellingError->get_CorrectiveAction(&correctiveAction);
    }

    if (SUCCEEDED(hr))
    {
        wchar_t misspelled[MAX_PATH];
        hr = StringCchCopyN(misspelled, ARRAYSIZE(misspelled), text + startIndex, errorLength);

        if (SUCCEEDED(hr))
        {
            // L"%s [%u, %u] is misspelled. ", misspelled, startIndex, startIndex + errorLength - 1);

            if(CORRECTIVE_ACTION_GET_SUGGESTIONS == correctiveAction)
            {
                // Suggestions
                IEnumString* enumSuggestions = nullptr;
                hr = spellChecker->Suggest(misspelled, &enumSuggestions);
                if (SUCCEEDED(hr))
                {
                    hr = getEnumString(enumSuggestions, L"\t", vectorSuggestions);
                    enumSuggestions->Release();
                }
				else
				{
					gVectorErrors.push_back(L"Failed to find suggestions");
				}
            }
            else if (CORRECTIVE_ACTION_REPLACE == correctiveAction)
            {
                // It should be autocorrected to
                replacement = nullptr;
                hr = spellingError->get_Replacement(&replacement);
				if (FAILED(hr))
                {
					gVectorErrors.push_back(L"Failed to find replacement");
                    // L"\t%s\n\n", replacement
                    //CoTaskMemFree(replacement);
                }
            }
            else if (CORRECTIVE_ACTION_DELETE == correctiveAction)
            {
                // It should be deleted
            }
            else
            {
                // Invalid corrective action
            }
        }
    }

	if (FAILED(hr))
		gVectorErrors.push_back(L"getSpellingError failed.");

    return hr;
}

inline HRESULT getSpellingErrors(_In_ ISpellChecker* spellChecker, _In_ PCWSTR text, 
								 _Inout_ IEnumSpellingError* enumSpellingError, 
								 _Inout_ size_t& numErrors, _Inout_ std::vector<PCWSTR>&  vectorSuggestions, _Inout_ PWSTR& replacement)
{
    HRESULT hr = S_OK;
    numErrors = 0;
    while (S_OK == hr)
    {
        ISpellingError* spellingError = nullptr;
        hr = enumSpellingError->Next(&spellingError);
        if (S_OK == hr)
        {
            ++numErrors;
			hr = getSpellingError(spellChecker, text, spellingError, vectorSuggestions, replacement);
            spellingError->Release();
        }
    }

	if (FAILED(hr))
		gVectorErrors.push_back(L"getSpellingErrors failed");

    return (SUCCEEDED(hr) ? S_OK : hr);
}

HRESULT checkSpelling(_In_ ISpellCheckerFactory* spellCheckerFactory, _In_ PCWSTR languageTag, _In_ PCWSTR buffer, 
					  _Inout_ size_t& numOfErrors, _Inout_ std::vector<PCWSTR>&  vectorSuggestions, _Inout_ PWSTR& replacement)
{
    BOOL isSupported = FALSE;
    HRESULT hr = spellCheckerFactory->IsSupported(languageTag, &isSupported);
    if (SUCCEEDED(hr))
    {
        if (TRUE == isSupported)
        {
			ISpellChecker* spellChecker = nullptr;
            hr = spellCheckerFactory->CreateSpellChecker(languageTag, &spellChecker);
            if (SUCCEEDED(hr))
            {
                wchar_t text[MAX_PATH];
				IEnumSpellingError* enumSpellingError = nullptr;
				HRESULT hr = ReadText(buffer, ARRAYSIZE(text), text);
				if (SUCCEEDED(hr))
				{
					hr = spellChecker->Check(text, &enumSpellingError);
				}
				else
				{
					gVectorErrors.push_back(L"Failed to read text from buffer");
				}

				if (SUCCEEDED(hr))
				{
					hr = getSpellingErrors(spellChecker, text, enumSpellingError, numOfErrors, vectorSuggestions, replacement);
					enumSpellingError->Release();
				}
				else
				{
					gVectorErrors.push_back(L"Failed to check spelling error");
				}
            }

            if (nullptr != spellChecker)
            {
                spellChecker->Release();
            }
        }
        else
        {
			gVectorErrors.push_back(L"Language is not supported.");
        }
    }

	if(FAILED(hr))
		gVectorErrors.push_back(L"checkSpelling failed.");

	return hr;
}



HRESULT CreateSpellCheckerFactory(_COM_Outptr_ ISpellCheckerFactory** spellCheckerFactory)
{
    HRESULT hr = CoCreateInstance(__uuidof(SpellCheckerFactory), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(spellCheckerFactory));
    if (FAILED(hr))
    {
        *spellCheckerFactory = nullptr;
		gVectorErrors.push_back(L"Unable to create Spell Checker Factory");
    }
    return hr;
}

HRESULT createSpellCheckerFactory(ISpellCheckerFactory*& spellCheckerFactory, bool& bWasCOMInitialized)
{
	// Reset errror messages 
	gVectorErrors.clear();

	int originalOutputMode = _setmode(_fileno(stdout), _O_U16TEXT);
    HRESULT hr = ((-1 == originalOutputMode) ? E_FAIL : S_OK);
    if (SUCCEEDED(hr))
    {
		// IT GIVES HRESULT - Cannot set the threading model once it is set.
		// IGNORING IT assuming it is able to cointialize but not setting threading mode. <More Research Needed>
        hr = CoInitializeEx(nullptr, COINIT_SPEED_OVER_MEMORY);
    }

	bWasCOMInitialized = SUCCEEDED(hr);		

    spellCheckerFactory = nullptr;
    if (hr != S_FALSE)
    {
        hr = CreateSpellCheckerFactory(&spellCheckerFactory);
    }
	else
	{
		gVectorErrors.push_back(L"COM Initialize Failed");
	}

	return hr;
}

void closeSpellCheckerFactory(ISpellCheckerFactory* spellCheckerFactory, bool bWasCOMInitialized)
{
	 if (nullptr != spellCheckerFactory)
    {
        spellCheckerFactory->Release();
    }

    if (bWasCOMInitialized)
    {
        CoUninitialize();
    }
}


// Get available languages 
int GetAvailableLanguages(std::vector<PCWSTR>& vectorAvailableLanguages)
{
	int success = SUCCESS;
	ISpellCheckerFactory* spellCheckerFactory = nullptr;
	bool bWasCOMInitialized = false;
	HRESULT hr = createSpellCheckerFactory(spellCheckerFactory, bWasCOMInitialized);
    if (SUCCEEDED(hr))
    {
		IEnumString* enumLanguages = nullptr;
		HRESULT hr = spellCheckerFactory->get_SupportedLanguages(&enumLanguages);
		if (SUCCEEDED(hr) && enumLanguages != nullptr)
		{
			HRESULT hr = S_OK;
			while (S_OK == hr)
			{
				LPOLESTR string = nullptr;
				hr = enumLanguages->Next(1, &string, nullptr);
				if (S_OK == hr)
				{
					vectorAvailableLanguages.push_back(string);
					//CoTaskMemFree(string);
				}
			}
		}
		else
		{
			gVectorErrors.push_back(L"Failed to get supported languages");
		}

		if (nullptr != enumLanguages)
		{
			enumLanguages->Release();
		}
    }

	closeSpellCheckerFactory(spellCheckerFactory, bWasCOMInitialized);

	if (FAILED(hr))
    {
		gVectorErrors.push_back(L"Supported Languages cannot be found");
		success = FAIL;
    }
	return success;
}


int CheckSpelling(PCWSTR word, PCWSTR const languageTag, 
				  _Inout_ bool& isSpellingCorrect,  
				  _Inout_ std::vector<PCWSTR>&  vectorSuggestions, _Inout_ PWSTR&  replacement)
{
	int success = SUCCESS;
	ISpellCheckerFactory* spellCheckerFactory = nullptr;
	bool bWasCOMInitialized = false;
	HRESULT hr = createSpellCheckerFactory(spellCheckerFactory, bWasCOMInitialized);
    if (SUCCEEDED(hr))
    {
		size_t numOfErrors = 0;
		hr = checkSpelling(spellCheckerFactory, languageTag, word, numOfErrors, vectorSuggestions, replacement);	
		if (SUCCEEDED(hr))
		{
			isSpellingCorrect = (numOfErrors == 0 ? true : false);
		}
		else
		{
			gVectorErrors.push_back(L"Failed to get supported languages");
		}
    }

	closeSpellCheckerFactory(spellCheckerFactory, bWasCOMInitialized);

	if (FAILED(hr))
    {
		gVectorErrors.push_back(L"Supported Languages cannot be found");
		success = FAIL;
    }
	return success;
}